package game.combat;

import action.Action;
import action.ActionAbility;
import misc.Vector3i;
import board.Tile;
import action.ActionMove;
import action.ActionAttack;
import board.BoardUtil;
import character.CharacterCombatControl;
import effect.character.single.SingleCharacterEffect;
import effect.EffectSpatial;
import character.MyCharacter;
import character.MyCharacterControl;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.input.controls.Trigger;
import com.jme3.math.FastMath;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import effect.AreaEffect;
import game.CombatUtil;
import game.CursorControl;
import game.DataStore;
import game.MyInputListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

public class TurnInputHandler implements ActionListener, AnalogListener, MyInputListener {
    protected Map<String, Trigger> inputMappings = new HashMap<String, Trigger>();

    public TurnInputHandler() {
        inputMappings.put("Left", new KeyTrigger(KeyInput.KEY_A));
        inputMappings.put("Right", new KeyTrigger(KeyInput.KEY_D));
        inputMappings.put("Up", new KeyTrigger(KeyInput.KEY_W));
        inputMappings.put("Down", new KeyTrigger(KeyInput.KEY_S));
        inputMappings.put("RandomMove", new KeyTrigger(KeyInput.KEY_E));
        inputMappings.put("EndTurn", new KeyTrigger(KeyInput.KEY_RETURN));
        inputMappings.put("Ability1", new KeyTrigger(KeyInput.KEY_1));
        inputMappings.put("Ability2", new KeyTrigger(KeyInput.KEY_2));
        inputMappings.put("Ability3", new KeyTrigger(KeyInput.KEY_3));
        inputMappings.put("Ability4", new KeyTrigger(KeyInput.KEY_4));
        inputMappings.put("Ability5", new KeyTrigger(KeyInput.KEY_5));
        inputMappings.put("Ability6", new KeyTrigger(KeyInput.KEY_6));
        inputMappings.put("Ability7", new KeyTrigger(KeyInput.KEY_7));
        inputMappings.put("Ability8", new KeyTrigger(KeyInput.KEY_8));
        inputMappings.put("Ability9", new KeyTrigger(KeyInput.KEY_9));
        inputMappings.put("Ability0", new KeyTrigger(KeyInput.KEY_0));
        
        inputMappings.put("Select", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
        inputMappings.put("Action", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
        
        for(Entry<String, Trigger> mapping : inputMappings.entrySet()) {
            if(!DataStore.inputManager.hasMapping(mapping.getKey()))
                DataStore.inputManager.addMapping(mapping.getKey(), mapping.getValue());
        }
    }
    
    public void register() {
        for(String event : inputMappings.keySet())
            DataStore.inputManager.addListener(this, event);
        
        if(DataStore.player.hasSelectedCharacter() && DataStore.player.getSelectedCharacter().equals(DataStore.combatAppState.getCurrentCombatActor())) {
            DataStore.board.highlightCharacterMoveLocations(DataStore.player.getSelectedCharacter());
        }
    }
    
    public void onAnalog(String name, float value, float tpf) {
        //override cam controls
	if(name.equals("Left")) {
	    
	} else if(name.equals("Right")) {
	    
	} else if(name.equals("Up")) {
	    
	} else if(name.equals("Down")) {
	    
	}
    }

    public void onAction(String name, boolean isPressed, float tpf) {
        if(isPressed) {
	    if(name.equals("Select")) {
		doMouseSelect();
	    } else if(name.equals("Action")) {
		doMouseAction();
	    } else if(name.equals("RandomMove")) {
                doRandomMove();
            } else if(name.equals("Ability1")) {
                selectEffect(1);
            } else if(name.equals("Ability2")) {
                selectEffect(2);
            } else if(name.equals("Ability3")) {
                selectEffect(3);
            } else if(name.equals("Ability4")) {
                selectEffect(4);
            } else if(name.equals("Ability5")) {
                selectEffect(5);
            } else if(name.equals("Ability6")) {
                selectEffect(6);
            } else if(name.equals("Ability7")) {
                selectEffect(7);
            } else if(name.equals("Ability8")) {
                selectEffect(8);
            } else if(name.equals("Ability9")) {
                selectEffect(9);
            } else if(name.equals("Ability0")) {
                selectEffect(0);
            } else if(name.equals("EndTurn")) {
                deselectAll();
                DataStore.combatAppState.endCharacterTurn();
            }
        }
    }

    protected void doMouseSelect() {
	deselectAll();
	CollisionResult target = rayCastFromMouseFirstCollision(DataStore.rootNode);
        if(target != null) {
            if(getAncestorOfType(target.getGeometry(), MyCharacter.class) != null) {
		DataStore.player.setSelectedCharacter(getAncestorOfType(target.getGeometry(), MyCharacter.class));
                if(DataStore.player.getSelectedCharacter().equals(DataStore.combatAppState.getCurrentCombatActor())) {
                    DataStore.board.highlightCharacterMoveLocations(DataStore.player.getSelectedCharacter());
                }
		System.out.println("selected: "+DataStore.player.getSelectedCharacter());
	    }
        }
    }

    protected void doMouseAction() {
        if(checkSelectedCurrentCharacter()) {
	    CollisionResult target = rayCastFromMouseFirstCollision(DataStore.rootNode);
	    if(target != null) {
                if(DataStore.player.hasSelectedEffectSpatial()) {
                    doAbility(target);
                } else {
		    if(getAncestorOfType(target.getGeometry(), MyCharacter.class) != null) {
			doAttack(getAncestorOfType(target.getGeometry(), MyCharacter.class));
		    } else if(getAncestorOfType(target.getGeometry(), Tile.class) != null) {
                        doMove(getAncestorOfType(target.getGeometry(), Tile.class));
                    }
                }
            }
        }
    }
    
    protected void doMove(Tile targetTile) {
        MyCharacter selectedCharacter = DataStore.player.getSelectedCharacter();
        if(selectedCharacter.getControl(MyCharacterControl.class).getValidMoveLocations().contains(targetTile.getBoardLocation())) {
            List<ActionMove> moveActions = new LinkedList<ActionMove>();
            Collection<Vector3i> travelPath = BoardUtil.findPath(selectedCharacter.getBoardLocation(), targetTile.getBoardLocation(), selectedCharacter.getCharacterStats().getMoveSpeedWithModifier(), selectedCharacter.getCharacterStats().getDiagonalMoveSpeedWithModifier());
            for(Vector3i waypoint : travelPath) {
                moveActions.add(new ActionMove(selectedCharacter, waypoint));
                selectedCharacter.setQueuedLocation(waypoint);
            }
            DataStore.board.highlightCharacterTravelPath(selectedCharacter, travelPath);
            queueCharacterAction(moveActions.toArray(new ActionMove[0]));
        } else {
            System.out.println("Not a valid move location!");
        }
    }
    
    protected void doAttack(MyCharacter targetChar) {
        if(!targetChar.getPlayer().equals(DataStore.player)) {
            if(CombatUtil.checkAttackRange(DataStore.player.getSelectedCharacter(), targetChar)) {
                queueCharacterAction(new ActionAttack(DataStore.player.getSelectedCharacter(), targetChar));
            } else {
                System.out.println("Out of range.");
            }
        }
    }
    
    protected void doAbility(CollisionResult target) {
        //First check to make sure we have a valid selection/target combination
        if((DataStore.player.getSelectedEffectSpatial().getEffect() instanceof SingleCharacterEffect && getAncestorOfType(target.getGeometry(), MyCharacter.class) != null)
                || (DataStore.player.getSelectedEffectSpatial().getEffect() instanceof AreaEffect && getAncestorOfType(target.getGeometry(), Tile.class) != null)) {
            if(DataStore.player.getSelectedEffectSpatial().getEffect() instanceof SingleCharacterEffect) {
                ((SingleCharacterEffect)DataStore.player.getSelectedEffectSpatial().getEffect()).setTarget(getAncestorOfType(target.getGeometry(), MyCharacter.class));
            }
            if(CombatUtil.checkEffectRange(DataStore.player.getSelectedEffectSpatial().getEffect())) {
                Action action = new ActionAbility(DataStore.player.getSelectedCharacter(), DataStore.player.getSelectedEffectSpatial().getEffect());
                DataStore.player.clearSelectedEffectSpatial(DataStore.player.getSelectedCharacter());
                queueCharacterAction(action);
            } else {
                System.out.println("Out of range.");
            }
        }
    }
    
    protected void doRandomMove() {
        if(checkSelectedCurrentCharacter()) {
            List<Vector3i> validMoveLocations = new ArrayList<Vector3i>(DataStore.player.getSelectedCharacter().getControl(CharacterCombatControl.class).getValidMoveLocations());
            queueCharacterAction(new ActionMove(DataStore.player.getSelectedCharacter(), validMoveLocations.get(FastMath.rand.nextInt(validMoveLocations.size()))));
        }
    }
    
    protected void selectEffect(int effectNum) {
        if(checkSelectedCurrentCharacter()) {
            MyCharacter character = DataStore.player.getSelectedCharacter();
            if(character.getCharacterStats().getAbility(effectNum) != null) {
                DataStore.player.clearSelectedEffectSpatial(character);
                DataStore.board.unhighlightCharacterMoveLocations(character);
                EffectSpatial effectSpatial = new EffectSpatial(character.getCharacterStats().getAbility(effectNum));
                effectSpatial.addControl(new CursorControl(DataStore.inputManager, DataStore.player.getCamera()));
                DataStore.board.addEffectSpatial(character, effectSpatial);
                DataStore.player.setSelectedEffectSpatial(effectSpatial);
            }
        }
    }
    
    protected boolean checkSelectedCurrentCharacter() {
        return DataStore.player.hasSelectedCharacter() && DataStore.player.getSelectedCharacter().equals(DataStore.combatAppState.getCurrentCombatActor());
    }
    
    /** This must be the last thing to execute for the turn! */
    protected void queueCharacterAction(Action... actions) {
        for(Action action : actions)
            DataStore.player.getSelectedCharacter().getControl(CharacterCombatControl.class).queueActionLast(action);
        DataStore.board.unhighlightCharacterMoveLocations(DataStore.player.getSelectedCharacter());
        DataStore.combatAppState.endCharacterTurnSegment();
    }

    public void deselectAll() {
	DataStore.player.clearSelectedEffectSpatial(DataStore.player.getSelectedCharacter());
	DataStore.player.clearSelectedCharacter();
    }
    
    public <T> T getAncestorOfType(Spatial spatial, Class<T> type) {
        if(spatial != null) {
            Spatial current = spatial;
            while(!(type.isInstance(current))) {
                if(current.getParent() != null)
                    current = current.getParent();
                else
                    return null;
            }
            return type.cast(current);
        } else {
            return null;
        }
    }

    protected CollisionResult rayCastFromMouseFirstCollision(Node onNode) {
        CollisionResults results = new CollisionResults();
	Vector2f click2d = DataStore.inputManager.getCursorPosition();
	Vector3f click3d = DataStore.player.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 0f).clone();
	Vector3f dir = DataStore.player.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 1f).subtractLocal(click3d).normalizeLocal();
	Ray ray = new Ray(click3d, dir);
        onNode.collideWith(ray, results);
        if (results.size() > 0) {
	  CollisionResult closest = results.getClosestCollision();
          return closest;
        }
        return null;
    }
}
